package com.superhelper.common.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.security.cert.CertificateException;
import javax.security.cert.X509Certificate;

import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 基于Apache的httpclient
 * 4.3.X以上的版本，使用PoolingHttpClientConnectionManager封装了HttpClient常用API。 <br>
 * 池化的参数设置：{@link HttpClientFactory}
 */
public class HttpClientHelper {

	private static final Logger log = LoggerFactory
			.getLogger(HttpClientHelper.class);

	private final RequestConfig defaultRequestConfig;

	private final CloseableHttpClient httpClient;
	private CloseableHttpClient httpsClient;

	/**
	 * 带参数的构造函数，通过工厂类{@link HttpClientFactory}生成指定的CSHttpClient,
	 * 
	 * @param factory
	 *            生成CSHttpClient的工厂类
	 */
	public HttpClientHelper(HttpClientFactory factory) {
		PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
		this.defaultRequestConfig = RequestConfig
				.custom()
				.setConnectTimeout(factory.getConnectionTimeout())
				.setConnectionRequestTimeout(
						factory.getConnectionRequestTimeout())
				.setSocketTimeout(factory.getSocketTimeOut()).build();
		cm.setMaxTotal(factory.getMaxTotal());
		cm.setDefaultMaxPerRoute(factory.getDefaultMaxPerRoute());
		this.httpClient = HttpClients.custom().setConnectionManager(cm).build();
		this.httpsClient = SSLClientWrapper.wrapClient(factory.getHttpClient());
	}

	/**
	 * 无参构造函数
	 */
	public HttpClientHelper() {
		this(new HttpClientFactory());
	}

	/**
	 * 获取池化的原生httpClient
	 * 
	 * @return httpClient
	 */
	public HttpClient getHttpClient() {
		return httpClient;
	}

	/**
	 * 获取池化的原生httpsClient
	 * 
	 * @return httpClient
	 */
	public HttpClient getHttpsClient() {
		return httpsClient;
	}

	/**
	 * 执行一个http方法
	 * 
	 * @param httpRequestBase
	 *            执行方法的类型
	 * @return response正确返回后的字符串
	 * @throws Exception
	 */
	public String executeMethod(HttpRequestBase httpRequestBase)
			throws Exception {
		CloseableHttpResponse response = null;
		StatusLine status = null;
		String result = "";

		try {
			setDefaultRequestConfig(httpRequestBase);
			log.debug("executing request "
					+ decodeUrl(httpRequestBase.getURI().toString()));
			if (httpRequestBase.getURI().toString().startsWith("https:")) {
				response = httpsClient.execute(httpRequestBase);
			} else {
				response = httpClient.execute(httpRequestBase);
			}

			status = response.getStatusLine();
			log.debug("response status " + status);
			HttpEntity entity = response.getEntity();
			if (status.getStatusCode() == HttpStatus.SC_OK) {
				result = inputStream2String(entity.getContent());
			} else {
				throw new Exception("get data from url:"
						+ httpRequestBase.getURI() + " fail, status: " + status);
			}
		} catch (ClientProtocolException e) {
			log.error("get data from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status, e);
			throw new Exception("get data from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status, e);
		} catch (IOException e) {
			log.error("get data from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status, e);
			throw new Exception("get data from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status, e);
		} finally {
			if (response != null) {
				try {
					response.close();
				} catch (IOException e) {
					log.error(
							"response close IOException:"
									+ httpRequestBase.getURI(), e);
				}
			}
			if (httpRequestBase != null) {
				httpRequestBase.releaseConnection();
			}
		}
		return UnicodeConverter.convertUnicode(result);
	}

	/**
	 * 执行一个http方法
	 * 
	 * @param httpRequestBase
	 *            执行方法的类型
	 * @return response正确返回后的byte[]
	 * @throws Exception
	 */
	public byte[] executeMethodAndReturnByteArray(
			HttpRequestBase httpRequestBase) throws Exception {
		CloseableHttpResponse response = null;
		StatusLine status = null;
		try {
			setDefaultRequestConfig(httpRequestBase);
			log.debug("executing request "
					+ decodeUrl(httpRequestBase.getURI().toString()));
			if (httpRequestBase.getURI().toString().startsWith("https:")) {
				response = httpsClient.execute(httpRequestBase);
			} else {
				response = httpClient.execute(httpRequestBase);
			}
			status = response.getStatusLine();
			log.debug("response status " + status);
			HttpEntity entity = response.getEntity();
			if (status.getStatusCode() == HttpStatus.SC_OK) {
				return inputStream2ByteArray(entity.getContent());
			} else {
				throw new Exception("get byte array from url:"
						+ httpRequestBase.getURI() + " fail, status: " + status);
			}
		} catch (ClientProtocolException e) {
			log.error("get byte array from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status, e);
			throw new Exception("get byte array from url:"
					+ httpRequestBase.getURI() + " fail, status: " + status, e);
		} catch (IOException e) {
			log.error("get byte array from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status, e);
			throw new Exception("get byte array from url:"
					+ httpRequestBase.getURI() + " fail, status: " + status, e);
		} finally {
			if (response != null) {
				try {
					response.close();
				} catch (IOException e) {
					log.error(
							"response close IOException:"
									+ httpRequestBase.getURI(), e);
				}
			}
			if (httpRequestBase != null) {
				httpRequestBase.releaseConnection();
			}
		}
	}

	/**
	 * 执行一个HttpGet方法
	 * 
	 * @param url
	 *            请求地址
	 * @return response正确返回后的字符串
	 * @throws Exception
	 */
	public String doGet(String url) throws Exception {
		HttpGet get = new HttpGet(url);
		String result = this.executeMethod(get);
		return result;
	}

	/**
	 * 执行一个HttpGet方法,返回response返回的流
	 * 
	 * @param url
	 *            ,请求地址
	 * @param statusArray
	 *            指点正确返回的状态码
	 * @return response正确返回后的流
	 * @throws Exception
	 */
	public InputStream getResponseStream(String url, int[] statusArray)
			throws Exception {
		HttpGet httpRequestBase = new HttpGet(url);
		CloseableHttpResponse response = null;
		StatusLine status = null;
		try {
			setDefaultRequestConfig(httpRequestBase);
			log.debug("executing request "
					+ decodeUrl(httpRequestBase.getURI().toString()));
			if (httpRequestBase.getURI().toString().startsWith("https:")) {
				response = httpsClient.execute(httpRequestBase);
			} else {
				response = httpClient.execute(httpRequestBase);
			}
			status = response.getStatusLine();
			log.debug("response status " + status);
			HttpEntity entity = response.getEntity();
			if (isInStatusArray(status.getStatusCode(), statusArray)) {
				byte[] array = EntityUtils.toByteArray(entity);
				ByteArrayInputStream bytes = new ByteArrayInputStream(array);
				return bytes;
			}
			throw new Exception("get data from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status);
		} catch (ClientProtocolException e) {
			log.error("get data from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status, e);
			throw new Exception("get data from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status, e);
		} catch (IOException e) {
			log.error("get data from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status, e);
			throw new Exception("get data from url:" + httpRequestBase.getURI()
					+ " fail, status: " + status, e);
		} finally {
			if (response != null) {
				try {
					response.close();
				} catch (IOException e) {
					log.error(
							"response close IOException:"
									+ httpRequestBase.getURI(), e);
				}
			}
			if (httpRequestBase != null) {
				httpRequestBase.releaseConnection();
			}
		}
	}

	/**
	 * 发送一个HttpGet请求，检查地址是否正常
	 * 
	 * @param url
	 *            地址
	 * @return boolean 'response返回响应状态码200或304'
	 */
	public boolean isGetOK(String url) {
		HttpGet get = new HttpGet(url);
		CloseableHttpResponse response = null;
		StatusLine status = null;
		try {
			setDefaultRequestConfig(get);
			log.debug("executing request " + decodeUrl(get.getURI().toString()));
			if (get.getURI().toString().startsWith("https:")) {
				response = httpsClient.execute(get);
			} else {
				response = httpClient.execute(get);
			}
			status = response.getStatusLine();
			log.debug("response status " + status);
			if (status.getStatusCode() == HttpStatus.SC_OK
					|| status.getStatusCode() == HttpStatus.SC_NOT_MODIFIED) {
				return true;
			}
			return false;
		} catch (ClientProtocolException e) {
			log.error("get data from url:" + get.getURI() + " fail, status: "
					+ status, e);
		} catch (Exception e) {
			log.error("get data from url:" + get.getURI() + " fail, status: "
					+ status, e);
		} finally {
			if (response != null) {
				try {
					response.close();
				} catch (IOException e) {
					log.error("response close IOException:" + get.getURI(), e);
				}
			}
			if (get != null) {
				get.releaseConnection();
			}
		}
		return false;
	}

	/**
	 * 执行一个HttPost请求
	 * 
	 * @param url
	 *            请求地址
	 * @param parameters
	 *            自动参数按utf-8编码
	 * @return response正确返回后的字符串
	 * @throws Exception
	 */
	public String doPost(String url, Map<String, String> parameters)
			throws Exception {

		HttpPost httpRequestBase = new HttpPost(url);
		httpRequestBase.setHeader("Content-Type",
				"application/x-www-form-urlencoded");
		if (parameters != null && !parameters.isEmpty()) {
			try {
				httpRequestBase.setEntity(new UrlEncodedFormEntity(
						toNameValuePairs(parameters), "UTF-8"));
			} catch (UnsupportedEncodingException e1) {
				throw new Exception(e1);
			}
		}
		return this.executeMethod(httpRequestBase);
	}

	public static List<NameValuePair> toNameValuePairs(
			Map<String, String> params) {

		ArrayList<NameValuePair> list = new ArrayList<NameValuePair>();
		Set<Entry<String, String>> entries = params.entrySet();
		for (Entry<String, String> entry : entries) {

			NameValuePair nameValuePair = new BasicNameValuePair(
					entry.getKey(), entry.getValue());
			list.add(nameValuePair);
		}
		return list;
	}

	/**
	 * 执行一个HttPost请求
	 * 
	 * @param url
	 *            请求地址
	 * @param jsonStr
	 *            json字符串, 按utf-8编码
	 * @return response正确返回后的字符串
	 * @throws Exception
	 */
	public String doPost(String url, String jsonStr) throws Exception {
		HttpPost httpRequestBase = new HttpPost(url);
		if (jsonStr != null && !jsonStr.isEmpty()) {
			httpRequestBase.setHeader("Content-Type", "application/json");
			httpRequestBase.setEntity(new StringEntity(jsonStr, "UTF-8"));
		}
		return this.executeMethod(httpRequestBase);
	}

	private String inputStream2String(InputStream in) throws IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 256);
		byte[] temp = new byte[1024 * 256];
		int i = -1;
		while ((i = in.read(temp)) != -1) {
			baos.write(temp, 0, i);
		}
		return baos.toString();
	}

	private byte[] inputStream2ByteArray(InputStream in) throws IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream(1024 * 256);
		byte[] temp = new byte[1024 * 256];
		int i = -1;
		while ((i = in.read(temp)) != -1) {
			baos.write(temp, 0, i);
		}
		return baos.toByteArray();
	}

	private void setDefaultRequestConfig(HttpRequestBase requestBase) {

		RequestConfig config = requestBase.getConfig();
		if (config == null) {
			requestBase.setConfig(defaultRequestConfig);
			return;
		}
		Builder builder = RequestConfig.custom();
		if (config.getConnectionRequestTimeout() == -1) {
			builder.setConnectionRequestTimeout(defaultRequestConfig
					.getConnectionRequestTimeout());
		}
		if (config.getConnectTimeout() == -1) {
			builder.setConnectTimeout(defaultRequestConfig.getConnectTimeout());
		}
		if (config.getSocketTimeout() == -1) {
			builder.setSocketTimeout(defaultRequestConfig.getSocketTimeout());
		}
		config = builder
				.setExpectContinueEnabled(config.isExpectContinueEnabled())
				.setStaleConnectionCheckEnabled(
						config.isStaleConnectionCheckEnabled())
				.setAuthenticationEnabled(config.isAuthenticationEnabled())
				.setRedirectsEnabled(config.isRedirectsEnabled())
				.setRelativeRedirectsAllowed(
						config.isRelativeRedirectsAllowed())
				.setCircularRedirectsAllowed(
						config.isCircularRedirectsAllowed())
				.setMaxRedirects(config.getMaxRedirects())
				.setCookieSpec(config.getCookieSpec())
				.setLocalAddress(config.getLocalAddress())
				.setProxy(config.getProxy())
				.setTargetPreferredAuthSchemes(
						config.getTargetPreferredAuthSchemes())
				.setProxyPreferredAuthSchemes(
						config.getProxyPreferredAuthSchemes()).build();
		requestBase.setConfig(config);
	}

	private boolean isInStatusArray(int status, int[] statusArray) {
		for (int i = 0; i < statusArray.length; i++) {
			if (status == statusArray[i]) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 关闭httpClient,关闭流并释放相关的系统资源
	 * 
	 * @throws IOException
	 */
	public void shutdown() throws IOException {
		httpClient.close();
	}

	/**
	 * 以 utf-8 编码 url，完善日志的输出
	 * 
	 * @param url
	 * @return
	 */
	private String decodeUrl(String url) {
		try {
			return URLDecoder.decode(url, "utf-8");
		} catch (UnsupportedEncodingException e) {
			log.error("cannot use urf-8 to decode url : {}", url);
			return url;
		}
	}

	/**
	 * 避免HttpClient的”SSLPeerUnverifiedException: peer not authenticated”异常
	 * 不用导入SSL证书
	 */
	// FIXME 以后用到https访问的时候，要注意重构
	public static class SSLClientWrapper {
		public static CloseableHttpClient wrapClient(
				org.apache.http.client.HttpClient base) {
			try {
				SSLContext ctx = SSLContext.getInstance("TLS");
				X509TrustManager tm = new X509TrustManager() {
					@Override
					public java.security.cert.X509Certificate[] getAcceptedIssuers() {
						return null;
					}

					public void checkClientTrusted(X509Certificate[] arg0,
							String arg1) throws CertificateException {
					}

					public void checkServerTrusted(X509Certificate[] arg0,
							String arg1) throws CertificateException {
					}

					@Override
					public void checkClientTrusted(
							java.security.cert.X509Certificate[] chain,
							String authType)
							throws java.security.cert.CertificateException {
						// TODO Auto-generated method stub
					}

					@Override
					public void checkServerTrusted(
							java.security.cert.X509Certificate[] chain,
							String authType)
							throws java.security.cert.CertificateException {
						// TODO Auto-generated method stub

					}
				};
				ctx.init(null, new TrustManager[] { tm }, null);
				SSLSocketFactory ssf = new SSLSocketFactory(ctx,
						SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
				SchemeRegistry registry = new SchemeRegistry();
				registry.register(new Scheme("https", 443, ssf));
				ThreadSafeClientConnManager mgr = new ThreadSafeClientConnManager(
						registry);
				return new DefaultHttpClient(mgr, base.getParams());
			} catch (Exception ex) {
				ex.printStackTrace();
				return null;
			}
		}
	}
}
